import _thread
import os
import pathlib

import time
import tkinter.font as font
import tkinter.messagebox as messagebox
from tkinter import filedialog

import keyboard as keyboard
import ttkbootstrap
from PIL import ImageTk, Image
from pymouse import PyMouse

from common import *
from check_app_tool import *
import create_project_tool
from entity.File import File
from entity.Operation import *
from entity.Project import Project
from image_tool import ImageTool
from set_run_config_tool import RunConfig
from det.det import *


class DDT(Window):
    def __init__(self):
        """
         #想要切换主题，修改theme值即可，有以下这么多的主题，自己尝试吧：
         ['vista', 'classic', 'cyborg', 'journal', 'darkly', 'flatly', 'clam', 'alt',
          'solar', 'minty', 'litera', 'united', 'xpnative', 'pulse', 'cosmo', 'lumen',
           'yeti', 'superhero', 'winnative', 'sandstone', 'default'
           ]
            原文链接：https://blog.csdn.net/Jacob6Lu/article/details/121519241
        """
        super().__init__()
        self.window = ttkbootstrap.Style(theme='darkly').master
        self.title = "开发者工具"
        self.project = None
        self.Image = None
        self.process = None

        """dom"""
        # image_tool抓图工具
        self.image_tool = None
        # 新建项目工具
        self.create_project_tool = None
        # 选择应用程序工具
        self.app = None
        # 配置运行文件
        self.run_config = None
        # 本地进程
        self.det = None

        """变量"""
        # 高度显示60个字符
        font_size = value_from_ratio(self.HEIGHT / 60)
        # 目录
        self.catalogue = None
        self.catalogue_width = int(self.WIDTH * 0.4)
        self.catalogue_height = int(20)
        # 文本
        self.text = None
        self.text_width = int(self.WIDTH * 0.6)
        # 根据分辨率计算字体
        self.text_height = value_from_ratio((self.HEIGHT * 0.5) / font_size)
        # 字体风格
        self.ft = font.Font(family="苹方", size=font_size)
        # 日志
        self.log = None
        self.log_width = int(self.WIDTH / font_size)
        self.log_height = value_from_ratio((self.HEIGHT * 0.5) / font_size)
        # 打开文件类型
        self.open_type = None

        """图标"""
        self.file = File(self, pathlib.Path("."))
        # 文件夹
        self.dir_logo = ImageTk.PhotoImage(Image.open("images/文件夹.png").resize((self.file.width, self.file.height)))
        # 配置文件
        self.conf_logo = ImageTk.PhotoImage(Image.open("images/设置.jpg").resize((self.file.width, self.file.height)))
        # 特征
        self.images = {}
        # 返回
        self.return_logo = ImageTk.PhotoImage(Image.open("images/返回.jpg").resize((self.file.width, self.file.height)))

    def init(self):
        self.Image = None
        self.process = None

        """dom"""
        # image_tool抓图工具
        self.image_tool = None
        # 新建项目工具
        self.create_project_tool = None
        # 选择应用程序工具
        self.app = None
        # 本地进程
        self.det = None

        # 打开文件类型
        self.open_type = None

    def set_thread(self):
        """线程管理"""
        # config运行信息监控
        _thread.start_new_thread(self.get_config_message, ())

    def get_config_message(self):
        while 1:
            if self.project is not None:
                try:
                    self.info_log(self.project.config.message_set.pop())
                except KeyError:
                    pass
                except AttributeError:
                    self.project.config.message_set = set()
            time.sleep(1)

    def get_image(self, path):
        img = ImageTk.PhotoImage(Image.open(path).resize((self.file.width, self.file.height)))
        self.images[path] = img

    def set_menu(self):
        """菜单栏功能"""
        menus = tk.Menu(self.window)
        self.window["menu"] = menus

        menus_project = tk.Menu(menus)
        menus_project.add_command(label="新项目", command=self.new_project)
        menus_project.add_command(label="打开项目", command=self.open_project)
        menus_project.add_command(label="打包项目", command=self.project_package)

        menus.add_cascade(label="项目", menu=menus_project)
        menus.add_cascade(label="选择应用程序", command=self.set_app)
        menus.add_cascade(label="保存当前文件 ctrl+S", command=self.save_text)
        menus.add_cascade(label="开始F10/终止F11", command=self.start)
        menus.add_cascade(label="操作手册", command=self.help)
        menus.add_cascade(label="抓图工具", command=self.image_tool_show)
        menus.add_cascade(label="获取坐标F2", command=self.catch)

    def project_package(self):
        """打包项目"""
        if self.project is None:
            messagebox.showinfo("提示", "还没有打开项目！请先打开")
            return 0
        _thread.start_new_thread(self.to_package, ())

    def to_package(self):
        _project_path = str(self.project.project)
        _res = os.system(f"7z.exe a "
                         f"{_project_path}.dsft.ini "
                         f"{_project_path} "
                         f"-mx9")
        if _res == 0:
            self.info_log(f"打包成功->{_project_path}.dsft.ini")
            return 1
        self.err_log(f"打包失败,请使用7z手动打包:"
                     f"7z.exe a "
                     f"{_project_path}.dsft.ini "
                     f"{_project_path} "
                     f"-mx9")
        return 0

    def catch(self, event):
        """获取鼠标点击坐标"""
        self.hide()
        keyboard.wait('F2')
        _res = PyMouse().position()
        _res = str(_res[0]) + "," + str(_res[1])
        self.window.withdraw()
        self.window.clipboard_clear()
        self.window.clipboard_append(_res)
        self.info_log(_res + " 已复制到剪切板Ctrl+v复制")
        self.window.update()
        self.show()

    def set_app(self):
        """选择应用程序"""
        if self.project is None:
            self.warn_log("还没有打开项目，请打开项目后再操作")
            return 0
        if self.app is not None:
            self.warn_log("已经打开了窗口了！")
            return 0
        self.app = tk.Toplevel()
        CheckApp(self)

    def save_text(self, event=None):
        """保存当前文件"""
        text = self.text.get("1.0", tk.END)
        if self.open_type:
            if self.project.config.set(text):
                self.project.config.save()
                self.info_log(f"保存成功 {self.project.config.config_path}")
                # 加载信息至ddt
                self.get_config_to_ddt()
                return 1
            self.err_log("保存失败，检查字段是否有缺失,双引号是否是英文？\n" + self.project.config.to_string())
            return 0
        try:
            if not self.Image.text_to_image(text):
                self.err_log("保存失败，操作的类型不符合要求")
                return 0
        except AttributeError:
            self.err_log("保存失败")
            self.warn_log("还没有打开任何图片特征")
            return 0
        if image_to_project(self.Image):
            self.info_log(f"保存成功 {self.Image.to_string()}")
            # 加载信息至ddt
            self.get_image_to_ddt()

    def help(self):
        """输出操作手册至日志"""
        self.info_log(Operation().help)

    def new_project(self):
        """新建项目"""
        self.create_project_tool = tk.Toplevel()
        create_project_tool.CreateProjectTool(self)

    def open_project(self):
        """打开项目"""
        config_path = filedialog.askopenfile(filetypes=[('app.dsft.ini', '.dsft.ini')])
        if config_path is None:
            return 0
        config_path = pathlib.Path(config_path.name)
        project_path = config_path.parent
        self.info_log(f"打开项目:{project_path}")
        self.project = Project(project_path, config_path)
        self.window.title(self.title + "  -" + self.project.name)
        self.load_project_information()
        self.project.config.read_config()
        self.init()
        if not self.project.config.init:
            self.set_app()
            return 0
        if len(self.project.config.run_order) != len(self.get_scene()):
            self.info_log("项目的运行配置还没有完成哦！按F10可以配置运行设置")
            return 0
        self.info_log("项目已完成配置，可以运行")
        return 1

    def get_scene(self):
        """获取场景的列表"""
        _list = []
        for i in self.project.project.rglob("*"):
            if i.is_dir():
                _list.append(i)
        return _list

    def get_image_to_ddt(self):
        """获取Image信息到DDT文本框"""
        self.Image.path = pathlib.Path(self.Image.path)
        self.load_catalogue(self.Image.path.parent)
        self.set_text_context(self.Image.to_string(), True)
        self.info_log(f"已打开特征文件{self.Image.path}")

    def get_config_to_ddt(self):
        """获取配置信息到DDT文本框"""
        self.load_catalogue(self.project.project)
        self.set_text_context(self.project.config.read_config())
        self.info_log(f"打开配置文件：{self.project.config.config_path}")

    def image_tool_show(self):
        """抓图工具"""
        if self.project is None:
            if messagebox.askokcancel("提示", "还没有打开项目，是否需要打开已有项目？"):
                if self.open_project():
                    self.image_tool_show()
            return 0
        self.image_tool = tk.Toplevel()
        self.Image = None
        ImageTool(self)
        # 窗口最小化
        self.window.state("iconic")
        # 窗口不可见
        self.window.wm_attributes("-alpha", 0)

    def set_catalogue(self):
        """目录栏"""
        self.catalogue = tk.Canvas(self.window, width=self.catalogue_width, height=self.catalogue_height)
        self.catalogue.grid(row=0, column=0, sticky=tk.NW)

    def return_parent(self):
        """返回主目录"""
        self.load_catalogue(self.project.project)

    def load_project_information(self):
        """加载所有功能"""
        # 目录栏
        self.load_catalogue(self.project.project)

    def rm_catalogue(self):
        """清除目录栏所有子控件"""
        _list = self.catalogue.winfo_children()
        for item in _list:
            if item.winfo_children():
                item.grid_remove()

    def load_catalogue(self, root):
        """加载目录栏信息"""
        self.rm_catalogue()
        self.images = {}
        row = 0
        column = -1
        for x in root.iterdir():
            if x.is_dir():
                row, column = self.replace(row, column)
                File(self, x).set_image_to_window(row, column, self.dir_logo)
            if x.is_file():
                if x.name == "app.dsft.ini":
                    row, column = self.replace(row, column)
                    File(self, x).set_image_to_window(row, column, self.conf_logo)
                if x.suffix == ".jpg":
                    row, column = self.replace(row, column)
                    self.get_image(x.absolute())
                    File(self, x).set_image_to_window(row, column, self.images[x.absolute()])
        if root != self.project.project:
            row, column = self.replace(row, column)
            File(self, "return").set_image_to_window(row, column, self.return_logo)

    def replace(self, row, column):
        number = int(self.catalogue_width / self.file.width)
        column = column + 1
        if column == number:
            row = row + 1
            column = 0
        return row, column

    def set_text(self):
        """文本功能"""
        self.text = tk.Text(self.window, width=self.text_width, height=self.text_height,
                            undo=True, autoseparators=False)
        self.text.configure(font=self.ft)
        self.text.grid(row=0, column=1, sticky=tk.NE)
        self.text.bind("<Control-z>", self.text_back)
        self.text.bind("<Key>", self.text_callback)
        self.text.bind("<Tab>", self.advise)

    def advise(self, event):
        """建议提示功能"""
        self.add_text_context(self.tab_advise())
        return "break"

    def tab_advise(self):
        """根据解析的结果给出建议"""
        if self.Image is None:
            self.err_log("没有打开任何文件！")
            return ""
        solve_dict = self.Image.solve_image(self.text.get("1.0", tk.END))
        if len(solve_dict["operation"]) == 0 or "delay_time" in solve_dict["operation"][-1]:
            self.text.delete(get_del_line(self.text), tk.END)
            operation_id = len(solve_dict["operation"])
            return f"\n[{operation_id}]\ntype = Click/Drag/Input # 请选择一个，Click点击/Drag拖拽/Input输入文本" \
                   f"\n按<Tab>键继续..."
        if solve_dict["operation"][-1]["type"] == "Click" and "delay_time" not in solve_dict["operation"][-1]:
            self.text.delete(get_del_line(self.text), tk.END)
            return f"\ndelay_time = 1.0 # 是一个浮点数，单位为秒，代表每个操作执行后的延迟" \
                   f"\ntranslate = 0,0 # 坐标，为0,0时代表识别特征的中心位置" \
                   f"\n按<Tab>键继续..."
        if solve_dict["operation"][-1]["type"] == "Drag" and "delay_time" not in solve_dict["operation"][-1]:
            self.text.delete(get_del_line(self.text), tk.END)
            return f"\ndelay_time = 1.0 # 是一个浮点数，单位为秒，代表每个操作执行后的延迟" \
                   f"\ntranslate = 0,0 # 坐标，为0,0时代表识别特征的中心位置" \
                   f"\ntranslate_drag = 0,0 # 拖拽到的坐标" \
                   f"\n按<Tab>键继续..."
        if solve_dict["operation"][-1]["type"] == "Input" and "delay_time" not in solve_dict["operation"][-1]:
            self.text.delete(get_del_line(self.text), tk.END)
            return f"\ndelay_time = 1.0 # 是一个浮点数，单位为秒，代表每个操作执行后的延迟" \
                   f"\ntext = [text/http] # 键盘模拟输入的文本[text/http]，默认为text" \
                   f"\n按<Tab>键继续..."
        self.err_log("请选择type值")
        return ""

    def text_back(self, event):
        """撤销"""
        if self.Image is None:
            return 0
        try:
            self.text.edit_undo()
        except Exception:
            self.warn_log("已经无法撤销了，你是否想要这些数据？")
            self.info_log(self.Image.to_string())

    def text_callback(self, event):
        """自动插入分隔符"""
        self.text.edit_separator()

    def set_text_context(self, message, is_image=False):
        """设置文本框内容"""
        self.text.delete("1.0", tk.END)
        if is_image:
            self.text.image_create(tk.END, image=self.images[self.Image.path])
            self.text.insert("end", "\n")
        self.text.insert("end", message)
        self.text.see("end")

    def add_text_context(self, message):
        """追加文本框内容"""
        self.text.insert("end", message)
        self.text.see("end")

    def set_log(self):
        """日志功能"""
        self.log = tk.Text(self.window, width=self.log_width * 2, height=13)
        self.log.grid(row=1, column=0, columnspan=2, sticky=tk.SW)
        self.log.configure(font=self.ft)
        # 注册字样
        # self.log.tag_config("info", foreground="black")
        self.log.tag_config("warn", foreground="yellow")
        self.log.tag_config("err", foreground="red")

    def info_log(self, message):
        """常规日志"""
        message = get_time_to_log() + message + "\n"
        self.log.insert("end", message)
        self.log.see("end")
        self.save_log(message)

    def warn_log(self, message):
        """警告日志"""
        message = get_time_to_log() + message + "\n"
        self.log.insert("end", message, "warn")
        self.log.see("end")
        self.save_log(message)

    def err_log(self, message):
        """错误日志"""
        message = get_time_to_log() + message + "\n"
        self.log.insert("end", message, "err")
        self.log.see("end")
        self.save_log(message)

    def save_log(self, message):
        """保存日志"""
        if self.project is None:
            return 0
        name = get_time_to_log().split(" ")[0] + ".log"
        log_path = self.project.project / name
        with open(log_path, "a+") as file:
            file.write(message)

    def set_run_config(self):
        """运行配置窗口"""
        self.run_config = tk.Toplevel()
        RunConfig(self)

    def check_run_config(self):
        """检查配置"""
        if self.project is None:
            self.warn_log("还没有载入任何项目信息，无法启动项目")
            return False
        if not self.check_end():
            if len(self.project.config.run_order) == 0:
                pass
            else:
                self.err_log("请检查运行配置，没有找到项目的退出跳转，项目必须有一个正常终止跳转")
                if not messagebox.askyesno("提示", "是否重新配置运行文件？"):
                    return False
        else:
            return True
        if not messagebox.askyesno("提示", "你将配置运行文件"):
            return False
        self.info_log("接下来请根据提示，选择和填入一些必要运行信息")
        self.project.config.run_order = []
        self.set_run_config()
        self.hide()

    def check_end(self):
        if len(self.project.config.run_order) == len(self.get_scene()):
            for i in self.project.config.run_order:
                for j in i["jump"]:
                    if i["jump"][j] == "正常终止":
                        return True
        return False

    def start(self, event=None):
        """开始脚本"""
        # 检查运行配置是否完成，如果没有，需要设置
        if not self.check_run_config():
            return 0
        self.info_log("运行配置文件检查通过，准备运行项目...现在跳转至det")
        if not messagebox.askyesno("提示", "请确定是否运行项目？终止请通过F11"):
            self.info_log("运行中断, 没有确定项目的运行提示")
            return False
        if self.project.config.app is False:
            self.warn_log("没有找到应用程序，请手动打开")
            return 0
        self.det = Det(ddt=self)
        self.det.run()
        _thread.start_new_thread(self.stop_event, ())

    def end(self):
        if not self.det.end:
            self.det.end = True
            self.info_log(f"正常退出，项目耗时："
                          f"{(time.time() - self.det.time)}秒")
            self.project.config.move_app()

    def err(self):
        if not self.det.end:
            self.det.end = True
            self.info_log(f"异常退出，项目耗时："
                          f"{(time.time() - self.det.time)}秒")
            self.project.config.move_app()

    def stop(self, event=None):
        """终止脚本"""
        if not self.det.end:
            self.det.end = True
            self.info_log(f"终止退出，项目耗时："
                          f"{(time.time() - self.det.time)}秒")

    def stop_event(self):
        while 1:
            keyboard.wait("F11")
            if self.det.end:
                return "end"
            self.stop()

    @staticmethod
    def exit(event=None):
        """退出程序"""
        sys.exit(1)

    def set_function(self):
        """重写功能"""
        # 设置菜单栏
        self.set_menu()
        # 设置目录栏
        self.set_catalogue()
        # 设置文本栏
        self.set_text()
        # 设置输出栏
        self.set_log()
        # 主窗口绑定事件
        self.window.bind("<Control-s>", self.save_text)
        self.window.bind("<F2>", self.catch)
        self.window.bind("<F10>", self.start)
        # 开启线程
        self.set_thread()
        # 窗口关闭事件
        self.window.protocol("WM_DELETE_WINDOW", self.exit)

    def run(self):
        """运行窗口"""
        self.set_window()
        self.window.mainloop()


"""全局变量"""
os.system("python screen_ratio.py")
with open("ratio.ini", "r") as f:
    # 屏幕分辨率缩放倍率
    SCREEN_RATIO = float(f.readline())


def value_from_ratio(value):
    return int(value / SCREEN_RATIO)


def main():
    DDT().run()


if __name__ == '__main__':
    main()
